package ink.xiaobaibai.util;

import ink.xiaobaibai.common.WxCommon;
import lombok.SneakyThrows;
import org.springframework.util.Base64Utils;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @description: 微信支付辅助信息
 * @author: 小白白
 * @create: 2021-05-21
 **/

public class WxPayKeyInfo {

    private static KeyStore store;

    private static final Object LOCK = new Object();

    private static KeyPair keyPair;

    private static X509Certificate x509Certificate;

    /**
     * 获取公私钥.
     *
     * @return the key pair
     */
    public static KeyPair getPKCS12() {
        if (keyPair == null) {
            initKeyPair();
        }
        return keyPair;
    }

    private static void initKeyPair() {
        File file = new File(WxCommon.KEYPATH);
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        char[] pem = WxCommon.KEYPASS.toCharArray();
        try {
            synchronized (LOCK) {
                if (store == null) {
                    synchronized (LOCK) {
                        store = KeyStore.getInstance("PKCS12");
                        store.load(inputStream, pem);
                    }
                }
            }
            X509Certificate certificate = (X509Certificate) store.getCertificate("Tenpay Certificate");
            certificate.checkValidity();
            x509Certificate = certificate;
            // 证书的 公钥
            PublicKey publicKey = certificate.getPublicKey();
            // 证书的私钥
            PrivateKey storeKey = (PrivateKey) store.getKey("Tenpay Certificate", pem);
            keyPair = new KeyPair(publicKey, storeKey);
        } catch (Exception e) {
            e.printStackTrace();
            throw new IllegalStateException("Cannot load keys from store: " + e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * V3  SHA256withRSA 签名. 辅助放到请求的header中
     *
     * @param method       请求方法  GET  POST PUT DELETE 等
     * @param canonicalUrl 例如  https://api.mch.weixin.qq.com/v3/pay/transactions/app?version=1 ——> /v3/pay/transactions/app?version=1
     * @param timestamp    当前时间戳   因为要配置到TOKEN 中所以 签名中的要跟TOKEN 保持一致
     * @param nonceStr     随机字符串  要和TOKEN中的保持一致
     * @param body         请求体 GET 为 "" POST 为JSON
     * @param keyPair      商户API 证书解析的密钥对  实际使用的是其中的私钥
     * @return the string
     */
    @SneakyThrows
    public static String sign(String method, String canonicalUrl, long timestamp, String nonceStr, String body, KeyPair keyPair) {
        String signatureStr = null;
        if (body != null) {
            signatureStr = Stream.of(method, canonicalUrl, String.valueOf(timestamp), nonceStr, body)
                    //将 集合 转为 String串 delimiter中间符 prefix前缀符 suffix后缀符
                    .collect(Collectors.joining("\n", "", "\n"));
        } else {
            signatureStr = Stream.of(method, canonicalUrl, String.valueOf(timestamp), nonceStr, "")
                    //将 集合 转为 String串 delimiter中间符 prefix前缀符 suffix后缀符
                    .collect(Collectors.joining("\n", "", "\n"));
        }
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(keyPair.getPrivate());
        sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
        return Base64Utils.encodeToString(sign.sign());
    }

    /**
     * 生成Token.  用于放到请求的header中
     *
     * @param mchId     商户号
     * @param nonceStr  随机字符串
     * @param timestamp 时间戳
     * @param serialNo  证书序列号
     * @param signature 签名  调用sign方法后获取的签名
     * @return the string
     */
    public static String getPayHeard(String mchId, String nonceStr, long timestamp, String serialNo, String signature) {
        final String TOKENPATTERN = "mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"";
        return String.format(TOKENPATTERN,
                mchId,
                nonceStr, timestamp, serialNo, signature);
    }

    /**
     * 解密响应体.
     *
     * @param apiV3Key       API V3 KEY  API v3密钥 商户平台设置的32位字符串
     * @param associatedData response.body.data[i].encrypt_certificate.associated_data // 附加数据包（可能为空）
     * @param nonce          response.body.data[i].encrypt_certificate.nonce // 加密使用的随机串初始化向量）
     * @param ciphertext     response.body.data[i].encrypt_certificate.ciphertext // Base64编码后的密文
     * @return the string
     * @throws GeneralSecurityException the general security exception
     */
    public static String decryptResponseBody(String apiV3Key, String associatedData, String nonce, String ciphertext) {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(StandardCharsets.UTF_8), "AES");
            GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8));
            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));
            byte[] bytes;
            try {
                bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext));
            } catch (GeneralSecurityException e) {
                throw new IllegalArgumentException(e);
            }
            //可以检验证书是否有效,但是我觉得没必要,因为5年之久,到时将新证书下进服务器中,写一个控制器使其重新读入即可
            return new String(bytes, StandardCharsets.UTF_8);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            e.printStackTrace();
            throw new IllegalStateException(e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            e.printStackTrace();
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * 构造验签名串.
     *
     * @param wechatpayTimestamp HTTP头 Wechatpay-Timestamp 中的应答时间戳。
     * @param wechatpayNonce     HTTP头 Wechatpay-Nonce 中的应答随机串
     * @param body               响应体
     * @return the string
     */
    public static String responseSign(String wechatpayTimestamp, String wechatpayNonce, String body) {
        return Stream.of(wechatpayTimestamp, wechatpayNonce, body)
                .collect(Collectors.joining("\n", "", "\n"));
    }

    /**
     * 我方对响应验签，和应答签名做比较，使用微信平台证书.
     *
     * @param wechatpaySerial    response.headers['Wechatpay-Serial']    当前使用的微信平台证书序列号
     * @param wechatpaySignature response.headers['Wechatpay-Signature']   微信平台签名
     * @param wechatpayTimestamp response.headers['Wechatpay-Timestamp']  微信服务器的时间戳
     * @param wechatpayNonce     response.headers['Wechatpay-Nonce']   微信服务器提供的随机串
     * @param body               response.body 微信服务器的响应体
     * @return the boolean
     */
    @SneakyThrows
    public static boolean responseSignVerify(String wechatpaySerial, String wechatpaySignature, String wechatpayTimestamp, String wechatpayNonce, String body) {

        Certificate certificate = (Certificate) x509Certificate;

        final String signatureStr = responseSign(wechatpayTimestamp, wechatpayNonce, body);
        Signature signer = Signature.getInstance("SHA256withRSA");
        signer.initVerify(certificate.getPublicKey());
        signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
        return signer.verify(Base64Utils.decodeFromString(wechatpaySignature));
    }

    //二次签名
    @SneakyThrows//主动try-catch住会出现的异常
    public static String signTwo(String appid, long timestamp, String nonceStr, String packageId, KeyPair keyPair) {
        String signatureStr = Stream.of(appid, String.valueOf(timestamp), nonceStr, packageId)
                .collect(Collectors.joining("\n", "", "\n"));
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(keyPair.getPrivate());
        sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
        return Base64Utils.encodeToString(sign.sign());
    }

}
